// =============================================================================
//      File:               Window.h
//      Created by:         Asteroth
//      Date:               28/11/08
// =============================================================================

#ifndef __ZEN_WINDOW_HPP__
#define __ZEN_WINDOW_HPP__

#define NOMINMAX

#define GLEW_STATIC
#include <GL/glew.h>
#if defined(LINUX)
	#include <GL/glxew.h>
#elif defined(WINDOWS)
	#include <GL/wglew.h>
#endif

#include <GL/glfw.h>

namespace zen {
namespace sys {

const int KEYBOARD_BUTTON_COUNT = GLFW_KEY_LAST - 1;
const int MOUSE_BUTTON_COUNT = GLFW_MOUSE_BUTTON_LAST - 1;

enum Key {
    kEscape             = GLFW_KEY_ESC,
    kEnter              = GLFW_KEY_ENTER,
    kLAlt				= GLFW_KEY_LALT,
    kRAlt				= GLFW_KEY_RALT,
    kLControl           = GLFW_KEY_LCTRL,
    kRControl           = GLFW_KEY_RCTRL,
    kLShift             = GLFW_KEY_LSHIFT,
    kRShift             = GLFW_KEY_RSHIFT,
    kLWin				= GLFW_KEY_LSUPER,
    kRWin				= GLFW_KEY_RSUPER,
    kF1                 = GLFW_KEY_F1,
    kF2                 = GLFW_KEY_F2,
    kF3                 = GLFW_KEY_F3,
    kF4                 = GLFW_KEY_F4,
    kF5                 = GLFW_KEY_F5,
    kF6                 = GLFW_KEY_F6,
    kF7                 = GLFW_KEY_F7,
    kF8                 = GLFW_KEY_F8,
    kF9                 = GLFW_KEY_F9,
    kF10                = GLFW_KEY_F10,
    kF11                = GLFW_KEY_F11,
    kF12                = GLFW_KEY_F12,
    kF13                = GLFW_KEY_F13,
    kF14                = GLFW_KEY_F14,
    kF15                = GLFW_KEY_F15,
    kF16                = GLFW_KEY_F16,
    kF17                = GLFW_KEY_F17,
    kF18                = GLFW_KEY_F18,
    kF19                = GLFW_KEY_F19,
    kF20                = GLFW_KEY_F20,
    kF21                = GLFW_KEY_F21,
    kF22                = GLFW_KEY_F22,
    kF23                = GLFW_KEY_F23,
    kF24                = GLFW_KEY_F24,
    kF54                = GLFW_KEY_F25,
    kTab                = GLFW_KEY_TAB,
    kSpace              = GLFW_KEY_SPACE,
    kBackspace          = GLFW_KEY_BACKSPACE,
    kUp                 = GLFW_KEY_UP,
    kDown               = GLFW_KEY_DOWN,
    kLeft               = GLFW_KEY_LEFT,
    kRight              = GLFW_KEY_RIGHT,
    kHome               = GLFW_KEY_HOME,
    kEnd                = GLFW_KEY_END,
    kPageUp             = GLFW_KEY_PAGEUP,
    kPageDown           = GLFW_KEY_PAGEDOWN,
    kInsert             = GLFW_KEY_INSERT,
    kDelete             = GLFW_KEY_DEL,
    kNum0               = GLFW_KEY_KP_0,
    kNum1               = GLFW_KEY_KP_1,
    kNum2               = GLFW_KEY_KP_2,
    kNum3               = GLFW_KEY_KP_3,
    kNum4               = GLFW_KEY_KP_4,
    kNum5               = GLFW_KEY_KP_5,
    kNum6               = GLFW_KEY_KP_6,
    kNum7               = GLFW_KEY_KP_7,
    kNum8               = GLFW_KEY_KP_8,
    kNum9               = GLFW_KEY_KP_9,
    kNumMultiply        = GLFW_KEY_KP_MULTIPLY,
    kNumDivide          = GLFW_KEY_KP_DIVIDE,
    kNumMinus           = GLFW_KEY_KP_SUBTRACT,
    kNumPlus            = GLFW_KEY_KP_ADD,
    kNumDecimal         = GLFW_KEY_KP_DECIMAL,
    kNumEqual			= GLFW_KEY_KP_EQUAL,
    kNumEnter			= GLFW_KEY_KP_ENTER,
    kNumLock            = GLFW_KEY_KP_NUM_LOCK,
    kCapsLock           = GLFW_KEY_CAPS_LOCK,
    kScrollLock         = GLFW_KEY_SCROLL_LOCK,
    kPause				= GLFW_KEY_PAUSE,
    kMenu               = GLFW_KEY_MENU,

    k1                  = 49,
    k2                  = 50,
    k3                  = 51,
    k4                  = 52,
    k5                  = 53,
    k6                  = 54,
    k7                  = 55,
    k8                  = 56,
    k9                  = 57,
    k0                  = 48,
    kMinus              = 189,
    kEqual              = 187,

    kQ                  = 'Q',
    kW                  = 'W',
    kE                  = 'E',
    kR                  = 'R',
    kT                  = 'T',
    kY                  = 'Y',
    kU                  = 'U',
    kI                  = 'I',
    kO                  = 'O',
    kP                  = 'P',
    kOpenBracket        = 219,
    kCloseBracket       = 221,
    kA                  = 'A',
    kS                  = 'S',
    kD                  = 'D',
    kF                  = 'F',
    kG                  = 'G',
    kH                  = 'H',
    kJ                  = 'J',
    kK                  = 'K',
    kL                  = 'L',
    kQuote              = 186,
    kDblQuote           = 222,
    kBackQuote          = 192,
    kBackSlash          = 220,
    kZ                  = 'Z',
    kX                  = 'X',
    kC                  = 'C',
    kV                  = 'V',
    kB                  = 'B',
    kN                  = 'N',
    kM                  = 'M',
    kSemicolon          = 188,
    kColon              = 190,
    kSlash              = 191

};

enum Button {
    bLeft               = GLFW_MOUSE_BUTTON_LEFT,
    bRight              = GLFW_MOUSE_BUTTON_RIGHT,
    bMiddle             = GLFW_MOUSE_BUTTON_MIDDLE,
    b1					= GLFW_MOUSE_BUTTON_1,
    b2					= GLFW_MOUSE_BUTTON_2,
    b3					= GLFW_MOUSE_BUTTON_3,
    b4					= GLFW_MOUSE_BUTTON_4,
    b5					= GLFW_MOUSE_BUTTON_5,
    b6					= GLFW_MOUSE_BUTTON_6,
    b7					= GLFW_MOUSE_BUTTON_7,
    b8					= GLFW_MOUSE_BUTTON_8
};

struct Position {
public:
	int x;
	int y;

	Position(): x(0), y(0) {};
	Position(int x, int y): x(x), y(y) {};
};

struct Size {
public:
	int width;
	int height;

	Size(): width(0), height(0) {};
	Size(int width, int height): width(width), height(height) {};
};

class Window {
private:
	friend void GLFWCALL keyboardCallback(int key, int action);
	friend void GLFWCALL mouseButtonCallback(int button, int action);
	friend void GLFWCALL mousePositionCallback(int x, int y);
	friend void GLFWCALL mouseWheelCallback(int position);
	friend void GLFWCALL windowResizeCallback(int width, int height);

	static bool fullscreen;
	static bool keyH[KEYBOARD_BUTTON_COUNT];
	static bool keyD[KEYBOARD_BUTTON_COUNT];
	static bool mouseH[MOUSE_BUTTON_COUNT];
	static bool mouseD[MOUSE_BUTTON_COUNT];
	static Position mousePos;
	static Position mouseVel;
	static int mouseW;
	static Size windowSize;
	static float aspectRatio;

	static bool initialized;

	static inline bool initialize();
	static inline void finalize();

public:
	static inline bool open(std::string caption = "GLFW Window",
		int windowX = 0, int windowY = 0,
		int windowWidth = 800, int windowHeight = 600,
		bool fullscreen = false, int oglMajorVersion = 3, int oglMinorVersion = 0);
	static inline void close();

	static inline bool isOpened();
	static inline bool isActive();
	static inline bool isMinimized();
	static inline bool isFullscreen();

	static inline bool update();
	static inline bool draw();

	static inline void minimize();
	static inline void restore();

	static inline bool keyDown(int key);
	static inline bool keyHit(int key);

	static inline bool mouseDown(int button);
	static inline bool mouseHit(int button);

	static inline int mouseX();
	static inline int mouseY();
	static inline int mouseVelocityX();
	static inline int mouseVelocityY();
	static inline Position mousePosition();
	static inline void setMousePosition(int x, int y);
	static inline int mouseWheel();
	static inline void setMouseWheel(int position);

	static inline float getAspectRatio();

	static inline int getWindowX();
	static inline int getWindowY();
	static inline int getWindowWidth();
	static inline int getWindowHeight();
	static inline Size getWindowSize();

	static inline unsigned int getScreenWidth();
	static inline unsigned int getScreenHeight();
	static inline unsigned int getScreenWidthMM();
	static inline unsigned int getScreenHeightMM();

	static inline void setWindowPosition(int x, int y);
	static inline void setWindowSize(int width, int height);
};

bool Window::initialized = false;
bool Window::fullscreen = false;
bool Window::keyH[KEYBOARD_BUTTON_COUNT] = { false };
bool Window::keyD[KEYBOARD_BUTTON_COUNT] = { false };
bool Window::mouseH[MOUSE_BUTTON_COUNT] = { false };
bool Window::mouseD[MOUSE_BUTTON_COUNT] = { false };
int Window::mouseW = 0;
Position Window::mousePos;
Position Window::mouseVel;
Size Window::windowSize;
float Window::aspectRatio = 1.333f;

void GLFWCALL keyboardCallback(int key, int action) {
	if (action == GLFW_PRESS) {
		Window::keyD[key] = true;
		Window::keyH[key] = true;
	} else {
		Window::keyD[key] = false;
		Window::keyH[key] = false;
	}
}

void GLFWCALL mouseButtonCallback(int button, int action) {
	if (action == GLFW_PRESS) {
		Window::mouseH[button] = true;
		Window::mouseD[button] = true;
	} else {
		Window::mouseH[button] = false;
		Window::mouseD[button] = false;
	}
}

void GLFWCALL mousePositionCallback(int x, int y) {
	Window::mouseVel.x = x - Window::mousePos.x;
	Window::mouseVel.y = y - Window::mousePos.y;
	Window::mousePos.x = x;
	Window::mousePos.y = y;
	if (Window::mousePos.x < 0)
		Window::mousePos.x = 0;
	if (Window::mousePos.x >= Window::windowSize.width)
		Window::mousePos.x = Window::windowSize.width - 1;
	if (Window::mousePos.y < 0)
		Window::mousePos.y = 0;
	if (Window::mousePos.y >= Window::windowSize.height)
		Window::mousePos.y = Window::windowSize.height - 1;
	if (Window::mousePos.x != x || Window::mousePos.y != y) {
		glfwSetMousePos(Window::mousePos.x, Window::mousePos.y);
	}
}

void GLFWCALL mouseWheelCallback(int position) {
	Window::mouseW = position;
}

void GLFWCALL windowResizeCallback(int width, int height) {
	Window::windowSize.width = width;
	Window::windowSize.height = height;
}

int GLFWCALL windowCloseCallback(void) {
	//Window::close();
	return GL_FALSE;
}

bool Window::initialize() {
	if (!initialized) {
		initialized = glfwInit() == GL_TRUE;
	}
	return initialized;
}

bool Window::open(std::string caption, int windowX, int windowY, int windowWidth, int windowHeight, bool fullscreen, int oglMajorVersion, int oglMinorVersion) {
	zen_call("Window::open");
	initialize();

	if (isOpened()) {
        close();
	}

	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, oglMajorVersion);
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, oglMinorVersion);
	glfwOpenWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
	glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);

	if (glfwOpenWindow(windowWidth, windowHeight, 8, 8, 8, 8, 24, 8, fullscreen ? GLFW_FULLSCREEN : GLFW_WINDOW) == GL_TRUE) {
		glfwSetWindowTitle(caption.c_str());
		glfwSetWindowPos(windowX, windowY);

		glfwSetKeyCallback(keyboardCallback);
		glfwSetMouseButtonCallback(mouseButtonCallback);
		glfwSetMousePosCallback(mousePositionCallback);
		glfwSetMouseWheelCallback(mouseWheelCallback);
		glfwSetWindowSizeCallback(windowResizeCallback);
		glfwSetWindowCloseCallback(windowCloseCallback);

		glfwDisable(GLFW_AUTO_POLL_EVENTS);

		windowSize.width = windowWidth;
		windowSize.height = windowHeight;
		Window::fullscreen = fullscreen;
		aspectRatio = (float) windowWidth / windowHeight;

		if (glewInit() != GLEW_OK) {
			zen_log("Error: Failed to initialize OpenGL context.");
			return false;
	        close();
		}
		return true;
	}

	zen_log("Error: Failed to open a system window.");
    return false;
}

void Window::close() {
	glfwCloseWindow();
	glfwTerminate();
}

bool Window::isOpened() {
    return glfwGetWindowParam(GLFW_OPENED) == GL_TRUE;
}

bool Window::isActive() {
    return glfwGetWindowParam(GLFW_ACTIVE) == GL_TRUE;
}

bool Window::isMinimized() {
    return glfwGetWindowParam(GLFW_ICONIFIED) == GL_TRUE;
}

bool Window::isFullscreen() {
    return fullscreen;
}

bool Window::update() {
	if (isOpened()) {
		glfwPollEvents();
		return true;
	}
	return false;
}

bool Window::draw() {
	if (isOpened()) {
		glfwSwapBuffers();
		return true;
	}
	return false;
}

void Window::minimize() {
	glfwIconifyWindow();
}

void Window::restore() {
	glfwRestoreWindow();
}

bool Window::keyDown(int key) {
    return keyD[key];
}

bool Window::keyHit(int key) {
	bool hit = keyH[key];
	keyH[key] = false;
	return hit;
}

bool Window::mouseDown(int button) {
    return mouseD[button];
}

bool Window::mouseHit(int button) {
	bool hit = mouseH[button];
	mouseH[button] = false;
	return hit;
}

int Window::mouseX() {
	return mousePos.x;
}

int Window::mouseY() {
	return mousePos.y;
}

int Window::mouseVelocityX() {
	return mouseVel.x;
}

int Window::mouseVelocityY() {
	return mouseVel.y;
}

Position Window::mousePosition() {
	return mousePos;
}

void Window::setMousePosition(int x, int y) {
	mousePos.x = x;
	mousePos.y = y;
	glfwSetMousePos(x, y);
}

int Window::mouseWheel() {
	return mouseW;
}

void Window::setMouseWheel(int position) {
	mouseW = position;
	glfwSetMouseWheel(position);
}

float Window::getAspectRatio() {
	return aspectRatio;
}

int Window::getWindowWidth() {
    return windowSize.width;
}

int Window::getWindowHeight() {
    return windowSize.height;
}

Size Window::getWindowSize() {
    return windowSize;
}

void Window::setWindowPosition(int x, int y) {
	glfwSetWindowPos(x, y);
}

void Window::setWindowSize(int width, int height) {
    glfwSetWindowSize(width, height);
    aspectRatio = (float) width / height;
}

}; //sys
}; //zen

#endif
